在Spring源码剖析的前三篇文章,我们介绍了ApplicationContext、Bean相关内容、BeanPostProcessor的内容;但从普遍反馈和自己事后阅读的体验来看,文章过长,没有重点,条理并不是特别清楚。想必是写作方式出了问题,最突出的莫过于流水账式写法,虽然写作的目的并不一定是写出好的文章,而是主要服务自己,但时间一长,自己也是个普通的读者,同样会看不大懂。
因此,写作方法是需要变更了:要突出条理和重点,如需大段源码讲解,可在文章最后增加源码解析一节,读者可选读。也就是说,长度还是那么长,但可读性增强了很多。
本文我们关注SpringBoot启动时做了什么,这是理解自动注解的基础,下一篇文章我们将探索自动注解的实现方式。
一个最简单的SpringBoot应用,可以是这样
1 |
|
那么重点落在SpringApplication
类和其run方法上。
SpringApplication
SpringApplication是一个大类,包含了所有应用启动流程。包含的主要步骤如下
- 创建
ApplicationContext
- 注册
CommandLinePropertySource
到Environment
,用于将命令行参数暴露成容器中的属性值 - 刷新容器,即调用容器的
refresh()
方法 - 创建
CommandLineRunner
并调用其run()
方法
关键点
SpringApplication能够启动三种类型的应用
- 普通的Servlet应用
- 响应式的Reactive应用
- 普通的非Web应用
不同的应用,会创建对应的ApplciationContext实现类,在ApplicationContext源码分析那一节有所描述
SpringApplication引入了一个新的临时容器,
Bootstrapper
、BootstrapRegistry
,用于在创建出ApplicationContext
实例之前管理启动阶段的BeanSpringApplication提供
ApplicationContextInitializer
接口,用于在ApplicationContext
刷新前修改它,这为我们提供了一个自定义ApplicationContext
的扩展点。向SpringApplication注册它的方式有两个
- 调用
SpringApplication.addInitializers()
方法手动注册,手写代码时候可用 - 放在
META-INF/spring.factories
中,自定义starter时可用
- 调用
SpringApplication的核心 ——
META-INF/spring.factories
,启动时,会从所有jar包的该文件中搜寻指定factory类型的实现,SpringBoot的诸多特性,比如自动配置,都是依赖于该机制实现。加载该文件有专门的类——SpringFactoriesLoader
。在SpringApplication中,加载的类有四种Bootstrapper
:SpringBoot启动阶段用ApplicationContextInitializer
:Spring上下文初始化器SpringApplicationRunListener
:SpringApplication启动时候的监听器ApplicationListener
:Spring事件监听器
这其中最重要的当属
ApplicationContextInitializer
,考虑到其能力,SpringBoot的诸多功能应该都是由其子类实现。如果我们要查看哪些初始化器生效了,可以去对应jar包的spring.factories下查看。比如要看自动配置,去spring-boot-autoconfigure包下的spring.factories文件下查看(我们将在下一篇文章研究这个)
SpringApplication提供的自定义点
SpringApplication启动时也支持高级写法,可自定义更多内容,再run
,比如
1 | fun main(args: Array<String>) { |
这是除了spring.factories外,Spring为我们提供了的额外定义方法,主要可自定义如下内容:
- 主类
WebApplicationType
- 是否允许Bean覆盖
- Bean是否延迟加载
- Banner的模式:不显示、显示在控制台、显示在日志中
- 命令行参数否写入Spring环境
- 是否将
ApplicationConversionService
添加到ApplicationContext
的转换服务中 - 添加
Bootstrapper
- 添加
BootstrapRegistryInitializer
- 设置默认属性,会被添加到环境变量中
- 设置额外的profile
- 设置
BeanNameGenerator
- 设置
ConfigurableEnvironment
- 添加其它需要被添加到容器的资源,主要是指Configuration的资源
- 设置
ResourceLoader
- 设置环境变量前缀,当从系统中获取环境变量时,将会应用该前缀
- 设置
ConfigurableApplicationContext
,相当于手动指定容器的类型了 - 设置
ApplicationContextFactory
- 设置
ApplicationContextInitializer
,即添加自定义的初始化器 - 添加
ApplicationListener
,即事件监听器 - 设置
ApplicationStartup
,该类并没有什么用,仅日志输出用
还缺啥
如上,加上后面的源码分析,我们只看到了SpringApplication
的启动流程,貌似并没有看到关键之所在
application.properties
配置文件的加载原理- 自动配置加载的原理
这些其实在spring-boot.jar
和spring-boot-autoconfigure.jar
中的META-INF/spring.factories
有指定,出于篇幅,我们下篇文章再讨论。
源码分析
这次源码分析我们单独放在一边
构造方法
1 | public SpringApplication(Class<?>... primarySources) { |
新出现的类
WebApplicationType
:一个枚举,包含三个值:SERVLET
、REACTIVE
、NONE
。用于决定应用是否需要启动特定类型的web服务器Bootstrapper
:用于初始化BootstrapRegistry
1
2
3
4
5
6
7
8
9public interface Bootstrapper {
/**
* Initialize the given {@link BootstrapRegistry} with any required registrations.
* @param registry the registry to initialize
*/
void intitialize(BootstrapRegistry registry);
}BootstrapRegistry
:启动注册器,在服务启动阶段到ApplicationContext
准备好这段时间有效;作用有两个:创建创建过程比较复杂的Bean、创建需要在ApplicationContext
准备好之前共享的Bean。可以理解为预加载容器,即SpringBoot
启动阶段使用的容器。ApplicationContextInitializer
:用于初始化ApplicationContext
,调用时机在AbstractApplicationContext.refresh()
之前。也就是说,在SpringBoot中,可以添加自己的
ApplicationContextInitializer
在容器初始化时修改内容。1
2
3
4
5
6
7
8
9public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
获取工厂对象 - getSpringFactoriesInstances
听这个方法,像是抽象工厂模式,但总体看下来不大像,如果我们只是把它们当成一种插件机制,会更加方便理解。
getSpringFactoriesInstances()
是贯穿SpringBoot启动的静态方法,用于加载指定类型的实例,来源是所有包下的META-INF/spring.factories
文件中指定的类型。其作用就是获取指定类型的所有实现类的实例。
我们首先温习一下spring.factories
文件长啥样
1 | org.springframework.boot.env.EnvironmentPostProcessor=\ |
然后再来看这个方法
1 | private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { |
理解上面那段代码的重点,又落在了SpringFactoriesLoader.loadFactoryNames(type, classLoader)
上。
1 | // 注意这个spring.factories的位置 |
检测WebApplicationType
1 | private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }; |
检测逻辑
- 类路径中只有
org.springframework.web.reactive.DispatcherHandler
时,是响应式应用 - 类路径中同时存在
javax.servlet.Servlet
、org.springframework.web.context.ConfigurableWebApplicationContext
时,是Servlet应用 - 否则,是普通应用
WebApplicationType有什么用
- 创建environment时决定创建什么类型的Environment:
private ConfigurableEnvironment getOrCreateEnvironment()
- 创建ApplicationContext时指定具体类型
检测主类
1 | private Class<?> deduceMainApplicationClass() { |
检测逻辑:从当前调用栈中,寻找main方法那一层,其所属类就是主类
run方法
这是应用启动的主要逻辑之所在
1 | public ConfigurableApplicationContext run(String... args) { |
获取SpringApplicationRunListener
1 | private SpringApplicationRunListeners getRunListeners(String[] args) { |
准备环境
1 | private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { |
创建ApplicationContext
1 | protected ConfigurableApplicationContext createApplicationContext() { |
准备容器
1 | private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, |
其它 - 注解排序
为组件排序的方法,我们发现哪儿都有它,这里关照一下:AnnotationAwareOrderComparator.sort()
。
1 | public class AnnotationAwareOrderComparator extends OrderComparator { |
所以,重点肯定在OrderComparator
的compare()
方法上。
1 | public int compare( { Object o1, Object o2) |
可以总结出优先级顺序,两个组件
如果一个实现了
PriorityOrdered
接口,一个没有实现PriorityOrdered
接口,则实现了这个优先级更高PriorityOrdered
是Order
的子接口,没有任何附加实现也就是说,
PriorityOrdered
就是想实现一点:它比普通Ordered
接口具有更高的优先级如果都实现了
PriorityOrdered
,则根据其order值排序如果实现了
Ordered
或被@Order
注解,则根据其order值排序没有任何排序接口或注解的组件之间相互对比,永远是相等的
没有任何排序接口或注解的组件,和,有任意一个排序接口或注解的组件,后者优先级更高,除非后者主动指定order为
Ordered.LOWEST_PRECEDENCE